package org.zoomdy.gdbx;

import java.io.IOException;
import java.util.logging.Level;

import org.zoomdy.gdbx.os.RTOS;
import org.zoomdy.gdbx.os.eCos;
import org.zoomdy.gdbx.os.FreeRTOS;

public class OSNode extends Node
{
    final static String version = "1.2.0";
    
    String gdb;
    String exec;
    
    boolean isOpenOCD;
    boolean isJLink;
    boolean isQEMU;
    
    Debugger debugger;
    RTOS os;
    
    boolean vContDisable;
    
    public OSNode(String id)
    {
        super(id);
        
        this.gdb    = "gdb";
        this.exec   = "target.elf";
    }

    public String getGdb()
    {
        return gdb;
    }

    public void setGdb(String gdb)
    {
        this.gdb = gdb;
    }

    public String getExec()
    {
        return exec;
    }

    public void setExec(String exec)
    {
        this.exec = exec;
    }
    
    String getVersion()
    {
        return OSNode.version;
    }
    
    public String[] parseArgs(String[] args)
    {
        String[] thisNodeArgs = super.parseArgs(args);
        String[] remainArgs = new String[thisNodeArgs.length];
        int remainArgsIndex = 0;
        
        for(int i = 0; i < args.length; i++)
        {
            if(thisNodeArgs[i] == null)
            {
                break;
            }
            
            if(thisNodeArgs[i].startsWith("--gdb="))
            {
                this.setGdb(thisNodeArgs[i].split("=")[1]);
            }
            else if(thisNodeArgs[i].startsWith("--exec="))
            {
                this.setExec(thisNodeArgs[i].split("=")[1]);
            }
            else if(thisNodeArgs[i].equals("--openocd"))
            {
                this.isOpenOCD = true;
            }
            else if(thisNodeArgs[i].equals("--jlink"))
            {
                this.isJLink = true;
            }
            else if(thisNodeArgs[i].equals("--qemu"))
            {
                this.isQEMU = true;
                this.vContDisable = true;
            }
            else
            {
                remainArgs[remainArgsIndex] = thisNodeArgs[i];
                remainArgsIndex++;
            }
        }
        
        return remainArgs;
    }
    
    public String usage()
    {
        String s = super.usage();
        s += "--gdb=gdb        gdb executable filename\n";
        s += "      default: gdb\n";
        s += "--exec=exec      target executable filename\n";
        s += "      default: target.elf\n";
        s += "--openocd        fix some OpenOCD incompatible problem\n";
        s += "--jlink          fix some J-Link incompatible problem\n";
        s += "--qemu           fix some QEMU incompatible problem\n";
        
        return s;
    }
    
    public void detect()
    {
        this.os = null;
        
        this.debugger = new Debugger();
        this.debugger.setDebugLevel(this.getDebugLevel());
        if(this.debugger.open(this.gdb, this.exec))
        {
            do
            {
                this.os = new eCos(this.debugger, this);
                this.os.setDebugLevel(this.getDebugLevel());
                if(this.os.detect())
                {
                    this.debug(1, "eCos detected");
                    break;
                }
                
                this.os = new FreeRTOS(this.debugger, this);
                this.os.setDebugLevel(this.getDebugLevel());
                if(this.os.detect())
                {
                    this.debug(1, "FreeRTOS detected");
                    break;
                }
                
                this.os = null;
            }
            while(false);
        }
        else
        {
            System.err.println(this.debugger.getErrorMessage());
        }
        this.debugger.close();
        this.debugger = null;
        
        if(this.os == null)
        {
            System.err.println("no rtos detected");
        }
    }
    
    void doAccepted() throws IOException
    {
        super.doAccepted();
        this.detect();
    }
    
    Packet getThreadList(Packet packet) throws IOException
    {
        String threadList = this.os.getThreadList(packet.equals("qfThreadInfo"));
        return Packet.make(threadList);
    }
    
    Packet getThreadInfo(Packet packet) throws IOException
    {
        String id = packet.getString().split(",")[1];
        String threadInfo = this.os.getThreadInfo(Integer.parseInt(id, 16));
        if(threadInfo != null)
        {
            return Packet.makeHex(threadInfo);
        }
        else
        {
            return Packet.make("E00");
        }
    }
    
    Packet setCurrentThread(Packet packet) throws IOException
    {
        String opAndId = packet.getString().substring(1);
        String op, id;
        if(opAndId.startsWith("vCount"))
        {
            op = opAndId.substring(0, 6);
            id = opAndId.substring(6);
        }
        else
        {
            op = opAndId.substring(0, 1);
            id = opAndId.substring(1);
        }
        
        String reply;
        boolean ret = this.os.setCurrentThread(op, Integer.parseInt(id, 16));
        if(ret)
        {
            reply = "OK";
        }
        else
        {
            reply = "E00";
        }
        
        return Packet.make(reply);
    }
    
    Packet isAliveThread(Packet packet) throws IOException
    {
        String id = packet.getString().substring(1);
        boolean ret = this.os.isAliveThread(Integer.parseInt(id, 16));
        
        String reply;
        if(ret)
        {
            reply = "OK";
        }
        else
        {
            reply = "E00";
        }
        
        return Packet.make(reply);
    }
    
    Packet readRegisters(Packet packet) throws IOException
    {
        String regs = this.os.readRegisters();
        if(regs != null)
        {
            return Packet.make(regs);
        }
        else
        {
            return null;
        }
    }
    
    Packet querySupported(Packet packet) throws IOException
    {
        if(this.isOpenOCD)
        {
            return null;
        }
        
        Packet res = this.command(packet);
        
        String supported = res.getString();
        String[] supporteds = supported.split(";");
        String supportedFilter = "";
        for(String str: supporteds)
        {
            if(!str.equals("qXfer:threads:read+"))
            {
                supportedFilter += str + ";";
            }
        }
        supportedFilter = supportedFilter.substring(0, supportedFilter.length() - 1);
        
        return Packet.make(supportedFilter);
    }
    
    Packet queryCurrentThreadId(Packet packet) throws IOException
    {
        
        int id = this.os.getCurrentThread();
        if(id > 0)
        {
            return Packet.make("QC" + Integer.toHexString(id));
        }
        else
        {
            return Packet.make("OK");
        }
    }
    
    Packet vCont(Packet packet) throws IOException
    {
        if(this.vContDisable)
        {
            return Packet.make("");
        }
        else
        {
            return null;
        }
    }
    
    void processStopReply(Packet packet) throws IOException
    {
        this.sendAcknowledgment(this.CHANNEL_CLIENT);
        
        Packet stopReply = packet;
        int id = this.os.getCurrentThread();
        if(id > 0)
        {
            stopReply = Packet.make(packet.getString() + "thread:" + Integer.toHexString(id) + ";");
        }
        
        stopReply.channels = this.CHANNEL_SERVER;
        this.sendPacket(stopReply);
        this.recvAcknowledgment(this.CHANNEL_SERVER);
    }
    
    void processServerPacket(Packet packet) throws IOException
    {
        Packet responsePacket = null;
        
        if(this.os != null && packet.packetType == Packet.PacketType.HEX)
        {
            if(packet.isRunPacket())
            {
                this.os.targetRunning();
            }
            else if(packet.equals("qsThreadInfo") || packet.equals("qfThreadInfo"))
            {
                responsePacket = this.getThreadList(packet);
            }
            else if(packet.startsWith("qThreadExtraInfo"))
            {
                responsePacket = this.getThreadInfo(packet);
            }
            else if(packet.startsWith("T"))
            {
                responsePacket = this.isAliveThread(packet);
            }
            else if(packet.startsWith("H"))
            {
                responsePacket = this.setCurrentThread(packet);
            }
            else if(packet.startsWith("g"))
            {
                responsePacket = this.readRegisters(packet);
            }
            else if(packet.startsWith("qSupported"))
            {
                responsePacket = this.querySupported(packet);
            }
            else if(packet.startsWith("qC"))
            {
                responsePacket = this.queryCurrentThreadId(packet);
            }
            else if(packet.startsWith("vCont?"))
            {
                responsePacket = this.vCont(packet);
            }
            else
            {
                
            }
        }
        
        if(responsePacket == null)
        {
         // 如果数据包未进行任何处理，那么将源数据包转发给Client
            packet.channels = this.CHANNEL_CLIENT;
            this.sendPacket(packet);
        }
        else
        {
            // 如果数据包进行了处理，那么将响应包返回给Server，并处理Acknowledgment
            this.sendAcknowledgment(this.CHANNEL_SERVER);
            responsePacket.channels = this.CHANNEL_SERVER;
            this.sendPacket(responsePacket);
            this.recvAcknowledgment(this.CHANNEL_SERVER);
        }
    }
    
    void processClientPacket(Packet packet) throws IOException
    {
        boolean isProcessed = false;
        if(this.os != null && packet.packetType == Packet.PacketType.HEX)
        {
            if(packet.startsWith("T05") || packet.startsWith("T02"))
            {
                this.processStopReply(packet);
                isProcessed = true;
            }
        }
        
        if(!isProcessed)
        {
            packet.channels = this.CHANNEL_SERVER;
            this.sendPacket(packet);
        }
    }
    
    public void run()
    {
        assert this.serverPort > 0;
        assert this.clientHost != null;
        assert this.clientPort > 0;
        
        // 不需要扩展通道，扩展通道强制成NONE模式
        this.extenderMode = Node.ExtenderMode.NONE;
        
        try
        {
            while(this.nodeState != NodeState.QUIT)
            {
                this.logger.entering("org.zoomdy.gdbx.OSNode", "run");
                
                Packet packet = this.recvPacket(this.CHANNEL_ANY);
                if(packet == null)
                {
                    continue;
                }
                
                if(packet.channels == this.CHANNEL_SERVER)
                {
                    this.processServerPacket(packet);
                }
                else if(packet.channels == this.CHANNEL_CLIENT)
                {
                    this.processClientPacket(packet);
                }
                else
                {
                    // nothing
                }
                
                this.logger.exiting("org.zoomdy.gdbx.OSNode", "run");
            }
        }
        catch(IOException e) 
        {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        OSNode node = new OSNode("os-node");

        String[] remainArgs = node.parseArgs(args);
        if(remainArgs.length > 0 && remainArgs[0] != null)
        {
            for(int i = 0; i < remainArgs.length; i++)
            {
                if(remainArgs[i] == null)
                {
                    break;
                }
                
                System.out.println("unrecognized option: " + remainArgs[i]);
            }
            System.out.print(node.usage());
            return;
        }
        
        node.setLoggerLevel(Level.WARNING);
        node.run();
    }
}
